﻿using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using UnityEngine;

/// <summary>
/// Convenience class for creating a Python engine integrated with Unity.
///
/// All scripts executed by the ScriptEngine created by this class will:
/// * Redirect output to Unity's console
/// * Be able to import any class in a `UnityEngine*` namespace
/// * Be able to import any class in a `UnityEditor*` namespace, if the script
///   is running in the UnityEditor
/// </summary>
public static class UnityPython
{
	public static bool IsPythonFile(string path){
		return System.IO.Path.GetExtension (path) == ".py";
	}

	//This types assembies will be loaded to python runtime engine
	//we need to mention a lot of UnityEngine classes because
	//starting from 2017.2 is now separated to many smaller assemblies
	static System.Type[] c_mustHaveTypes = {
		typeof(UnityPython),
		typeof(UnityEngine.GameObject),
		typeof(UnityEngine.Rigidbody),
		typeof(UnityEngine.AI.NavMeshAgent),
		//...
	};

	static ScriptEngine m_engine;
	public static ScriptEngine GetEngine(){
		if (m_engine == null) {
			m_engine = Python.CreateEngine ();
			var assemblys = c_mustHaveTypes.Select(t => t.Assembly).Distinct();
			List<Assembly> assemblyList = assemblys.ToList();
			assemblyList.AddRange(GetAssembliesWithName(new List<string>()
				{"Assembly-CSharp","Assembly-CSharp-first"}));
			foreach (var assembly in assemblyList) {
				if(assembly != null)
					m_engine.Runtime.LoadAssembly (assembly);
			}
			if (Application.isEditor)
			{
				ScriptEngine m_engine = UnityPython.GetEngine();
				var paths = m_engine.GetSearchPaths();

				string packagePath = Application.dataPath.Replace("Assets", "Packages/com.unity.python");
				paths.Add(packagePath + "/Lib");
				paths.Add(packagePath + "/Lib/site-packages");
				m_engine.SetSearchPaths (paths);
			}
		}
		return m_engine;
	}


	public const string defaultPythonConsoleHeader =
@"import clr
clr.AddReference('UnityEngine','System', 'Assembly-CSharp')
from UnityEngine import *

import System.Single
def float(x):
	return clr.Convert(x, System.Single)

import UnityEngine
Destroy = UnityEngine.Object.Destroy
FindObjectOfType = UnityEngine.Object.FindObjectOfType
FindObjectsOfType = UnityEngine.Object.FindObjectsOfType
DontDestroyOnLoad = UnityEngine.Object.DontDestroyOnLoad
DestroyImmediate = UnityEngine.Object.DestroyImmediate
Instantiate = UnityEngine.Object.Instantiate
import sys
sys.stdout=console
Select = console.Select
Clear = console.Clear
";

	public const string defaultPythonBehaviourHeader =
@"
import clr
clr.AddReference('UnityEngine','System', 'Assembly-CSharp')
from UnityEngine import *

import System.Single
def float(x):
	return clr.Convert(x, System.Single)

import UnityEngine
Destroy = UnityEngine.Object.Destroy
FindObjectOfType = UnityEngine.Object.FindObjectOfType
FindObjectsOfType = UnityEngine.Object.FindObjectsOfType
DontDestroyOnLoad = UnityEngine.Object.DontDestroyOnLoad
DestroyImmediate = UnityEngine.Object.DestroyImmediate
Instantiate = UnityEngine.Object.Instantiate

Update = None
Awake = None
Start = None
OnEnable = None
OnDisable = None
OnDestroy = None
OnCollisionEnter = None
OnCollisionStay = None
OnCollisionExit = None
";
	
        /// <summary>
        /// Including internal namespace to python engine
        /// </summary>
        /// <param name="identifier">Namespace Identifier</param>
        public static void IncludeNamespace(this ScriptEngine engine, string identifier)
        {
            foreach (var assembly in GetAssembliesInNamespace(identifier))
            {
                engine.Runtime.LoadAssembly(assembly);
            }
        }

        /// <summary>
        /// Including native python libraries path
        /// </summary>
        /// <param name="path">Path to python files</param>
        public static void IncludeLibraryPath(this ScriptEngine engine, string path)
        {
            var paths = engine.GetSearchPaths();
            paths.Add(path);
            engine.SetSearchPaths(paths);
        }

        /// <summary>
        /// Including standart unity libraries :: UnityEngine and UnityEditor
        /// </summary>
        public static void IncludeUnityLibraries(this ScriptEngine engine)
        {
            engine.IncludeNamespace("UnityEngine");
        #if UNITY_EDITOR
            engine.IncludeNamespace("UnityEditor");
        #endif
        }

        /// <summary>
        /// 如果没有拷贝过，将streamingassets下的python/lib拷贝的硬件地址
        /// </summary>
        /// <param name="engine"></param>
        public static void IncludeNativeLibrary(this ScriptEngine engine)
        {
            var library = Application.streamingAssetsPath + "/python/lib";
            var path = Application.persistentDataPath + "/python/lib";
            Directory.CreateDirectory(path);
            System.IO.File.Copy(library,path);
            engine.IncludeLibraryPath(path);
        }

        /// <summary>
        /// Getting assembly list from namespace
        /// </summary>
        /// <param name="Namespace">Namespace Identifier</param>
        /// <returns>Collection of Assemly</returns>
        public static IEnumerable<Assembly> GetAssembliesInNamespace(string Namespace)
        {
            return AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(t => t.GetTypes())
                .Where(t => t.Namespace != null && t.Namespace.StartsWith(Namespace))
                .Select(t => t.Assembly)
                .Distinct();
        }
        
        /// <summary>
        /// Getting assembly list from namespace
        /// </summary>
        /// <param name="identifier">Namespace Identifier</param>
        /// <returns>Collection of Assemly</returns>
        public static List<Assembly> GetAssembliesWithName(List<string> assemblyNames)
        {
	        return AppDomain.CurrentDomain.GetAssemblies()
		        .Where(t => assemblyNames.Contains(t.GetName().Name)).ToList();
        }
        /// <summary>
        /// create a python engine instance
        /// </summary>
        /// <param name="options"></param>
        /// <returns></returns>
        public static ScriptEngine CreateEngine(IDictionary<string, object> options = null)
        {
	        var engine = Python.CreateEngine(options);

	        // Redirect IronPython IO
	        var infoStream = new MemoryStream();
	        var infoWriter = new UnityLogWriter(Debug.Log, infoStream);
	        engine.Runtime.IO.SetOutput(infoStream, infoWriter);

	        var errorStream = new MemoryStream();
	        var errorWriter = new UnityLogWriter(Debug.LogError, errorStream);
	        engine.Runtime.IO.SetErrorOutput(errorStream, errorWriter);

	        // Load assemblies for the `UnityEngine*` namespaces
	        foreach (var assembly in GetAssembliesInNamespace("UnityEngine"))
	        {
		        engine.Runtime.LoadAssembly(assembly);
	        }

	        // Load assemblies for the `UnityEditor*` namespaces
	        if (Application.isEditor)
	        {
		        foreach (var assembly in GetAssembliesInNamespace("UnityEditor"))
		        {
			        engine.Runtime.LoadAssembly(assembly);
		        }
	        }

	        return engine;
        }
}
